diff --git a/doc/Makefile.am b/doc/Makefile.am
index 283b992a96..c88229ff28 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,106 +1,109 @@
#
# doc: Pacemaker code
#
# Copyright (C) 2008 Andrew Beekhof
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
MAINTAINERCLEANFILES = Makefile.in
+helpdir = $(datadir)/$(PACKAGE)
+
ascii = crm_cli.txt crm_fencing.txt
+help_DATA = crm_cli.txt
docbook = Pacemaker_Explained
man_MANS = cibadmin.8 crm_resource.8
doc_DATA = README.hb2openais $(ascii) $(generated_docs)
publican_docs =
generated_docs =
XML_FILES := $(wildcard *.xml)
PNG_FILES := $(wildcard images/*.png)
if BUILD_ASCIIDOC
generated_docs += $(ascii:%.txt=%.html)
endif
if BUILD_DOCBOOK
publican_docs += $(docbook)
endif
EXTRA_DIST = $(man_MANS) $(docbook:%=%.xml)
index.html:
echo "Building documentation index"
echo "
The following Pacemaker documentation was generated on `date` from version: $(BUILD_VERSION)
" > index.html
echo "" >> index.html
for doc in $(generated_docs); do \
echo "
" >> index.html
echo "" >> index.html
%.html: %.txt
$(ASCIIDOC) --unsafe --backend=xhtml11 $<
%.txt: %/en-US/*.xml
cd $* && $(PUBLICAN) build --publish --langs=all --formats=pdf,html,html-single,txt
cp $*/publish/en-US/Pacemaker/1.0/txt/$*/$@ $@
if BUILD_DOCBOOK
docbook_txt = $(docbook:%=%.txt)
all-local: $(docbook_txt)
#install-data-local: all-local
install-data-local: all-local
for book in $(docbook); do \
filelist=`find $$book/publish -print`; \
for f in $$filelist; do \
p=`echo $$f | sed s:publish/:: | sed s:Pacemaker/::`; \
if [ -d $$f ]; then \
echo $(INSTALL) -d 775 $(DESTDIR)/$(docdir)/$$p; \
else \
echo $(INSTALL) -m 644 $$f $(DESTDIR)/$(docdir)/$$p; \
fi \
done; \
done
endif
push: all-local index.html
echo Uploading current documentation set to clusterlabs.org
rsync -rtz --progress index.html root@oss.clusterlabs.org:/srv/www/extras/doc/
if BUILD_DOCBOOK
for book in $(docbook); do \
echo Uploading $$book...; \
rsync -rtz --progress --delete $$book/publish/* root@oss.clusterlabs.org:/srv/www/extras/doc/; \
done
endif
clean-local:
-rm -rf $(generated_docs) $(docbook)/tmp $(docbook)/publish
diff --git a/tools/crm.in b/tools/crm.in
index 196961952a..9abe6a9f33 100644
--- a/tools/crm.in
+++ b/tools/crm.in
@@ -1,7602 +1,7602 @@
#!/usr/bin/python
#
# Copyright (C) 2008 Dejan Muhamedagic
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import shlex
import os
from tempfile import mkstemp
import subprocess
import sys
import time
import readline
import copy
import xml.dom.minidom
import signal
import re
import glob
def is_program(prog):
return subprocess.call("which %s >/dev/null 2>&1"%prog, shell=True) == 0
def prereqs():
proglist = "which cibadmin crm_resource crm_attribute crm_mon crm_standby crm_failcount"
for prog in proglist.split():
if not is_program(prog):
print >> sys.stderr, "%s not available, check your installation"%prog
sys.exit(1)
prereqs()
lineno = -1
regression_tests = False
class ErrorBuffer(object):
'''
Show error messages either immediately or buffered.
'''
def __init__(self):
self.msg_list = []
self.mode = "immediate"
def buffer(self):
self.mode = "keep"
def release(self):
if self.msg_list:
print >> sys.stderr, '\n'.join(self.msg_list)
if not batch:
try:
raw_input("Press enter to continue... ")
except EOFError:
pass
self.msg_list = []
self.mode = "immediate"
def writemsg(self,msg):
if self.mode == "immediate":
if regression_tests:
print msg
else:
print >> sys.stderr, msg
else:
self.msg_list.append(msg)
def error(self,s):
self.writemsg("ERROR: %s" % add_lineno(s))
def warning(self,s):
self.writemsg("WARNING: %s" % add_lineno(s))
def info(self,s):
self.writemsg("INFO: %s" % add_lineno(s))
def debug(self,s):
if user_prefs.get_debug():
self.writemsg("DEBUG: %s" % add_lineno(s))
err_buf = ErrorBuffer()
def add_lineno(s):
if lineno > 0:
return "%d: %s" % (lineno,s)
else: return s
def common_err(s):
err_buf.error(s)
def common_warn(s):
err_buf.warning(s)
def common_info(s):
err_buf.info(s)
def common_debug(s):
err_buf.debug(s)
def no_prog_err(name):
err_buf.error("%s not available, check your installation"%name)
def missing_prog_warn(name):
err_buf.warning("could not find any %s on the system"%name)
def no_attribute_err(attr,obj_type):
err_buf.error("required attribute %s not found in %s"%(attr,obj_type))
def bad_def_err(what,msg):
err_buf.error("bad %s definition: %s"%(what,msg))
def unsupported_err(name):
err_buf.error("%s is not supported"%name)
def no_such_obj_err(name):
err_buf.error("%s object is not supported"%name)
def obj_cli_err(name):
err_buf.error("object %s cannot be represented in the CLI notation"%name)
def missing_obj_err(node):
err_buf.error("object %s:%s missing (shouldn't have happened)"% \
(node.tagName,node.getAttribute("id")))
def constraint_norefobj_err(constraint_id,obj_id):
err_buf.error("constraint %s references a resource %s which doesn't exist"% \
(constraint_id,obj_id))
def obj_exists_err(name):
err_buf.error("object %s already exists"%name)
def no_object_err(name):
err_buf.error("object %s does not exist"%name)
def invalid_id_err(obj_id):
err_buf.error("%s: invalid object id"%obj_id)
def id_used_err(node_id):
err_buf.error("%s: id is already in use"%node_id)
def skill_err(s):
err_buf.error("%s: this command is not allowed at this skill level"%' '.join(s))
def syntax_err(s,token = '',context = ''):
pfx = "syntax"
if context:
pfx = "%s in %s" %(pfx,context)
if type(s) == type(''):
err_buf.error("%s near <%s>"%(pfx,s))
elif token:
err_buf.error("%s near <%s>: %s"%(pfx,token,' '.join(s)))
else:
err_buf.error("%s: %s"%(pfx,' '.join(s)))
def bad_usage(cmd,args):
err_buf.error("bad usage: %s %s"%(cmd,args))
def empty_cib_err():
err_buf.error("No CIB!")
def cib_parse_err(msg):
err_buf.error("%s"%msg)
def cib_no_elem_err(el_name):
err_buf.error("CIB contains no '%s' element!"%el_name)
def cib_ver_unsupported_err(validator,rel):
err_buf.error("CIB not supported: validator '%s', release '%s'"% (validator,rel))
err_buf.error("You may try the upgrade command")
def update_err(obj_id,cibadm_opt,xml):
if cibadm_opt == '-U':
task = "update"
elif cibadm_opt == '-D':
task = "delete"
else:
task = "replace"
err_buf.error("could not %s %s"%(task,obj_id))
err_buf.info("offending xml: %s" % xml)
def not_impl_info(s):
err_buf.info("%s is not implemented yet" % s)
def ask(msg):
print_msg = True
while True:
try:
ans = raw_input(msg + ' ')
except EOFError:
ans = 'n'
if not ans or ans[0].lower() not in ('n','y'):
if print_msg:
print "Please answer with y[es] or n[o]"
print_msg = False
else:
return ans[0].lower() == 'y'
def keyword_cmp(string1, string2):
return string1.lower() == string2.lower()
from UserDict import DictMixin
class odict(DictMixin):
def __init__(self, data=None, **kwdata):
self._keys = []
self._data = {}
def __setitem__(self, key, value):
if key not in self._data:
self._keys.append(key)
self._data[key] = value
def __getitem__(self, key):
if key not in self._data:
return self._data[key.lower()]
return self._data[key]
def __delitem__(self, key):
del self._data[key]
self._keys.remove(key)
def keys(self):
return list(self._keys)
def copy(self):
copyDict = odict()
copyDict._data = self._data.copy()
copyDict._keys = self._keys[:]
return copyDict
class olist(list):
def __init__(self, keys):
#print "Init %s" % (repr(keys))
super(olist, self).__init__()
for key in keys:
self.append(key)
self.append(key.upper())
def help_short(s):
r = re.search("help_[^,]+,(.*)\]\]", s)
return r and r.group(1) or ''
class HelpSystem(object):
'''
The help system. All help is in the following form in the
manual:
[[cmdhelp__,]]
=== ...
Long help text.
...
[[cmdhelp__,]]
Help for the level itself is like this:
[[cmdhelp_,]]
'''
- help_text_file = "@docdir@/crm_cli.txt"
+ help_text_file = "@datadir@/@PACKAGE@/crm_cli.txt"
index_file = "%s/%s" % (os.getenv("HOME"),".crm_help_index")
def __init__(self):
self.key_pos = {}
self.key_list = []
self.no_help_file = False # don't print repeatedly messages
self.bad_index = False # don't print repeatedly warnings for bad index
def open_file(self,name,mode):
try:
f = open(name,mode)
return f
except IOError,msg:
common_err("%s open: %s"%(name,msg))
common_err("extensive help system is not available")
self.no_help_file = True
return None
def drop_index(self):
common_info("removing index")
os.unlink(self.index_file)
self.key_pos = {}
self.key_list = []
self.bad_index = True
def mk_index(self):
'''
Prepare an index file, sorted by topic, with seek positions
Do we need a hash on content?
'''
if self.no_help_file:
return False
help_f = self.open_file(self.help_text_file,"r")
if not help_f:
return False
idx_f = self.open_file(self.index_file,"w")
if not idx_f:
return False
common_info("building help index")
key_pos = {}
while 1:
pos = help_f.tell()
s = help_f.readline()
if not s:
break
if s.startswith("[["):
r = re.search(r'..([^,]+),', s)
if r:
key_pos[r.group(1)] = pos
help_f.close()
l = key_pos.keys()
l.sort()
for key in l:
print >>idx_f, '%s %d' % (key,key_pos[key])
idx_f.close()
return True
def is_index_old(self):
try:
t_idx = os.path.getmtime(self.index_file)
except:
return True
try:
t_help = os.path.getmtime(self.help_text_file)
except:
return True
return t_help > t_idx
def load_index(self):
if self.is_index_old():
self.mk_index()
self.key_pos = {}
idx_f = self.open_file(self.index_file,"r")
if not idx_f:
return False
for s in idx_f:
a = s.split()
if len(a) != 2:
if not self.bad_index:
common_err("index file corrupt")
idx_f.close()
self.drop_index()
return self.load_index() # this runs only once
return False
self.key_pos[a[0]] = long(a[1])
idx_f.close()
self.key_list = self.key_pos.keys()
self.key_list.sort()
return True
def __filter(self,s):
if '<<' in s:
return re.sub(r'<<[^,]+,(.+)>>', r'\1', s)
else:
return s
def __find_key(self,key):
low = 0
high = len(self.key_list)-1
while low <= high:
mid = (low + high)/2
if self.key_list[mid] > key:
high = mid - 1
elif self.key_list[mid] < key:
low = mid + 1
else:
return mid
return -1
def __load_help_one(self,key,skip = 2):
longhelp = ''
self.help_f.seek(self.key_pos[key])
shorthelp = help_short(self.help_f.readline())
for i in range(skip-1):
self.help_f.readline()
l = []
for s in self.help_f:
if s.startswith("[[") or s.startswith("="):
break
l.append(self.__filter(s))
if l and l[-1] == '\n': # drop the last line of empty
l.pop()
if l:
longhelp = ''.join(l)
if not shorthelp or not longhelp:
if not self.bad_index:
common_warn("help topic %s not found" % key)
self.drop_index()
return shorthelp,longhelp
def cmdhelp(self,s):
if not self.key_pos and not self.load_index():
return None,None
if not s in self.key_pos:
if not self.bad_index:
common_warn("help topic %s not found" % s)
self.drop_index()
return None,None
return self.__load_help_one(s)
def __load_level(self,lvl):
'''
For the given level, create a help table.
'''
if wcache.is_cached("lvl_help_tab_%s" % lvl):
return wcache.retrieve("lvl_help_tab_%s" % lvl)
if not self.key_pos and not self.load_index():
return None
self.help_f = self.open_file(self.help_text_file,"r")
if not self.help_f:
return None
lvl_s = "cmdhelp_%s" % lvl
if not lvl_s in self.key_pos:
if not self.bad_index:
common_warn("help table for level %s not found" % lvl)
self.drop_index()
return None
common_debug("loading help table for level %s" % lvl)
help_tab = odict()
help_tab["."] = self.__load_help_one(lvl_s)
lvl_idx = self.__find_key(lvl_s)
lvl_idx += 1
while lvl_idx < len(self.key_list):
key = self.key_list[lvl_idx]
if not key.startswith(lvl_s):
break
cmd = key[len(lvl_s)+1:]
help_tab[cmd] = self.__load_help_one(key)
lvl_idx += 1
self.help_f.close()
help_tab["quit"] = ("exit the program", "")
help_tab["help"] = ("show help", "")
help_tab["end"] = ("go back one level", "")
return help_tab
def load_level(self,lvl):
help_tab = self.__load_level(lvl)
if self.bad_index: # try again
help_tab = self.__load_level(lvl)
return wcache.store("lvl_help_tab_%s" % lvl, help_tab)
# from: http://code.activestate.com/recipes/475116/
class TerminalController(object):
"""
A class that can be used to portably generate formatted output to
a terminal.
`TerminalController` defines a set of instance variables whose
values are initialized to the control sequence necessary to
perform a given action. These can be simply included in normal
output to the terminal:
>>> term = TerminalController()
>>> print 'This is '+term.GREEN+'green'+term.NORMAL
Alternatively, the `render()` method can used, which replaces
'${action}' with the string required to perform 'action':
>>> term = TerminalController()
>>> print term.render('This is ${GREEN}green${NORMAL}')
If the terminal doesn't support a given action, then the value of
the corresponding instance variable will be set to ''. As a
result, the above code will still work on terminals that do not
support color, except that their output will not be colored.
Also, this means that you can test whether the terminal supports a
given action by simply testing the truth value of the
corresponding instance variable:
>>> term = TerminalController()
>>> if term.CLEAR_SCREEN:
... print 'This terminal supports clearning the screen.'
Finally, if the width and height of the terminal are known, then
they will be stored in the `COLS` and `LINES` attributes.
"""
# Cursor movement:
BOL = '' #: Move the cursor to the beginning of the line
UP = '' #: Move the cursor up one line
DOWN = '' #: Move the cursor down one line
LEFT = '' #: Move the cursor left one char
RIGHT = '' #: Move the cursor right one char
# Deletion:
CLEAR_SCREEN = '' #: Clear the screen and move to home position
CLEAR_EOL = '' #: Clear to the end of the line.
CLEAR_BOL = '' #: Clear to the beginning of the line.
CLEAR_EOS = '' #: Clear to the end of the screen
# Output modes:
BOLD = '' #: Turn on bold mode
BLINK = '' #: Turn on blink mode
DIM = '' #: Turn on half-bright mode
REVERSE = '' #: Turn on reverse-video mode
NORMAL = '' #: Turn off all modes
# Cursor display:
HIDE_CURSOR = '' #: Make the cursor invisible
SHOW_CURSOR = '' #: Make the cursor visible
# Terminal size:
COLS = None #: Width of the terminal (None for unknown)
LINES = None #: Height of the terminal (None for unknown)
# Foreground colors:
BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
# Background colors:
BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
_STRING_CAPABILITIES = """
BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
_COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
_ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
def __init__(self, term_stream=sys.stdout):
"""
Create a `TerminalController` and initialize its attributes
with appropriate values for the current terminal.
`term_stream` is the stream that will be used for terminal
output; if this stream is not a tty, then the terminal is
assumed to be a dumb terminal (i.e., have no capabilities).
"""
# Curses isn't available on all platforms
try: import curses
except:
common_info("no curses support: you won't see colors")
return
# If the stream isn't a tty, then assume it has no capabilities.
if not term_stream.isatty(): return
# Check the terminal type. If we fail, then assume that the
# terminal has no capabilities.
try: curses.setupterm()
except: return
# Look up numeric capabilities.
self.COLS = curses.tigetnum('cols')
self.LINES = curses.tigetnum('lines')
# Look up string capabilities.
for capability in self._STRING_CAPABILITIES:
(attrib, cap_name) = capability.split('=')
setattr(self, attrib, self._tigetstr(cap_name) or '')
# Colors
set_fg = self._tigetstr('setf')
if set_fg:
for i,color in zip(range(len(self._COLORS)), self._COLORS):
setattr(self, color, curses.tparm(set_fg, i) or '')
set_fg_ansi = self._tigetstr('setaf')
if set_fg_ansi:
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
set_bg = self._tigetstr('setb')
if set_bg:
for i,color in zip(range(len(self._COLORS)), self._COLORS):
setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
set_bg_ansi = self._tigetstr('setab')
if set_bg_ansi:
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
def _tigetstr(self, cap_name):
# String capabilities can include "delays" of the form "$<2>".
# For any modern terminal, we should be able to just ignore
# these, so strip them out.
import curses
cap = curses.tigetstr(cap_name) or ''
return re.sub(r'\$<\d+>[/*]?', '', cap)
def render(self, template):
"""
Replace each $-substitutions in the given template string with
the corresponding terminal control string (if it's defined) or
'' (if it's not).
"""
return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
def _render_sub(self, match):
s = match.group()
if s == '$$': return s
else: return getattr(self, s[2:-1])
def is_color(self, s):
try:
attr = getattr(self, s.upper())
return attr != None
except: return False
class CliDisplay(object):
"""
Display output for various syntax elements.
"""
def __init__(self):
self.no_pretty = False
def set_no_pretty(self):
self.no_pretty = True
def reset_no_pretty(self):
self.no_pretty = False
def colorstring(self, clrnum, s):
if self.no_pretty:
return s
else:
return termctrl.render("${%s}%s${NORMAL}" % \
(user_prefs.colorscheme[clrnum].upper(), s))
def keyword(self, kw):
s = kw
if "uppercase" in user_prefs.output:
s = s.upper()
if "color" in user_prefs.output:
s = self.colorstring(0, s)
return s
def otherword(self, n, s):
if "color" in user_prefs.output:
return self.colorstring(n, s)
else:
return s
def id(self, s):
return self.otherword(1, s)
def attr_name(self, s):
return self.otherword(2, s)
def attr_value(self, s):
return self.otherword(3, s)
def rscref(self, s):
return self.otherword(4, s)
def score(self, s):
return self.otherword(5, s)
global_aliases = {
"quit": ("bye","exit"),
"end": ("cd","up"),
}
def setup_aliases(obj):
for cmd in obj.cmd_aliases.keys():
for alias in obj.cmd_aliases[cmd]:
if obj.help_table:
obj.help_table[alias] = obj.help_table[cmd]
obj.cmd_table[alias] = obj.cmd_table[cmd]
#
# Resource Agents interface (meta-data, parameters, etc)
#
ocf_root = os.getenv("OCF_ROOT")
if not ocf_root:
ocf_root = "@OCF_ROOT_DIR@"
if not ocf_root:
ocf_root = "/usr/lib/ocf"
os.putenv("OCF_ROOT",ocf_root)
class RaLrmd(object):
'''
Getting information from the resource agents.
'''
lrmadmin_prog = "lrmadmin"
def __init__(self):
self.good = self.is_lrmd_accessible()
def lrmadmin(self, opts, xml = False):
'''
Get information directly from lrmd using lrmadmin.
'''
l = stdout2list("%s %s" % (self.lrmadmin_prog,opts))
if l and not xml:
l = l[1:] # skip the first line
return l
def is_lrmd_accessible(self):
if not (is_program(self.lrmadmin_prog) and is_process("lrmd")):
return False
return subprocess.call(\
add_sudo(">/dev/null 2>&1 %s -C" % self.lrmadmin_prog), \
shell=True) == 0
def meta(self, ra_class,ra_type,ra_provider):
return self.lrmadmin("-M %s %s %s"%(ra_class,ra_type,ra_provider),True)
def providers(self, ra_type,ra_class = "ocf"):
'List of providers for a class:type.'
return self.lrmadmin("-P %s %s" % (ra_class,ra_type),True)
def classes(self):
'List of providers for a class:type.'
return self.lrmadmin("-C")
def types(self, ra_class = "ocf", ra_provider = ""):
'List of types for a class.'
return self.lrmadmin("-T %s" % ra_class)
def os_types_list(path):
l = []
for f in glob.glob(path):
if os.access(f,os.X_OK) and os.path.isfile(f):
a = f.split("/")
l.append(a[-1])
return l
class RaOS(object):
'''
Getting information from the resource agents (direct).
'''
def __init__(self):
self.good = True
def meta(self, ra_class,ra_type,ra_provider):
l = []
if ra_class == "ocf":
l = stdout2list("%s/resource.d/%s/%s meta-data" % \
(ocf_root,ra_provider,ra_type))
elif ra_class == "stonith":
l = stdout2list("stonith -m -t %s" % ra_type)
return l
def providers(self, ra_type,ra_class = "ocf"):
'List of providers for a class:type.'
l = []
if ra_class == "ocf":
for s in glob.glob("%s/resource.d/*/%s" % (ocf_root,ra_type)):
a = s.split("/")
if len(a) == 7:
l.append(a[5])
return l
def classes(self):
'List of classes.'
return "heartbeat lsb ocf stonith".split()
def types(self, ra_class = "ocf", ra_provider = ""):
'List of types for a class.'
l = []
prov = ra_provider and ra_provider or "*"
if ra_class == "ocf":
l = os_types_list("%s/resource.d/%s/*" % (ocf_root,prov))
elif ra_class == "lsb":
l = os_types_list("/etc/init.d/*")
elif ra_class == "stonith":
l = stdout2list("stonith -L")
l = list(set(l))
l.sort()
return l
def ra_classes():
'''
List of RA classes.
'''
if wcache.is_cached("ra_classes"):
return wcache.retrieve("ra_classes")
l = ra_if.classes()
l.sort()
return wcache.store("ra_classes",l)
def ra_providers(ra_type,ra_class = "ocf"):
'List of providers for a class:type.'
id = "ra_providers-%s-%s" % (ra_class,ra_type)
if wcache.is_cached(id):
return wcache.retrieve(id)
l = ra_if.providers(ra_type,ra_class)
l.sort()
return wcache.store(id,l)
def ra_providers_all(ra_class = "ocf"):
'''
List of providers for a class.
'''
id = "ra_providers_all-%s" % ra_class
if wcache.is_cached(id):
return wcache.retrieve(id)
dir = ocf_root + "/resource.d"
l = []
for s in os.listdir(dir):
if os.path.isdir("%s/%s" % (dir,s)):
l.append(s)
l.sort()
return wcache.store(id,l)
def ra_types(ra_class = "ocf", ra_provider = ""):
'''
List of RA type for a class.
'''
if not ra_class:
ra_class = "ocf"
id = "ra_types-%s-%s" % (ra_class,ra_provider)
if wcache.is_cached(id):
return wcache.retrieve(id)
if ra_provider:
list = []
for ra in ra_if.types(ra_class):
if ra_provider in ra_providers(ra,ra_class):
list.append(ra)
else:
list = ra_if.types(ra_class)
list.sort()
return wcache.store(id,list)
def prog_meta(s):
'''
Do external program metadata.
'''
prog = "@CRM_DAEMON_DIR@/%s" % s
l = []
if is_program(prog):
l = stdout2list("%s metadata" % prog)
return l
def get_nodes_text(n,tag):
try:
node = n.getElementsByTagName(tag)[0]
for c in node.childNodes:
if c.nodeType == c.TEXT_NODE:
return c.data.strip()
except: return ''
def mk_monitor_name(role,depth):
depth = depth == "0" and "" or ("_%s" % depth)
return role and role != "Started" and \
"monitor_%s%s" % (role,depth) or \
"monitor%s" % depth
def monitor_name_node(node):
depth = node.getAttribute("depth") or '0'
role = node.getAttribute("role")
return mk_monitor_name(role,depth)
def monitor_name_pl(pl):
depth = find_value(pl, "depth") or '0'
role = find_value(pl, "role")
return mk_monitor_name(role,depth)
def crm_msec(t):
'''
See lib/common/utils.c:crm_get_msec().
'''
convtab = {
'ms': (1,1),
'msec': (1,1),
'us': (1,1000),
'usec': (1,1000),
'': (1000,1),
's': (1000,1),
'sec': (1000,1),
'm': (60*1000,1),
'min': (60*1000,1),
'h': (60*60*1000,1),
'hr': (60*60*1000,1),
}
if not t:
return -1
r = re.match("\s*(\d+)\s*([a-zA-Z]+)?", t)
if not r:
return -1
if not r.group(2):
q = ''
else:
q = r.group(2).lower()
try:
mult,div = convtab[q]
except:
return -1
return (int(r.group(1))*mult)/div
def crm_time_cmp(a, b):
return crm_msec(a) - crm_msec(b)
class RAInfo(object):
'''
A resource agent and whatever's useful about it.
'''
ra_tab = " " # four horses
required_ops = ("start", "stop")
skip_ops = ("meta-data", "validate-all")
skip_op_attr = ("name", "depth", "role")
def __init__(self,ra_class,ra_type,ra_provider = "heartbeat"):
self.ra_class = ra_class
self.ra_type = ra_type
self.ra_provider = ra_provider
if not self.ra_provider:
self.ra_provider = "heartbeat"
self.ra_node = None
def ra_string(self):
return self.ra_class == "ocf" and \
"%s:%s:%s" % (self.ra_class, self.ra_provider, self.ra_type) or \
"%s:%s" % (self.ra_class, self.ra_type)
def error(self, s):
common_err("%s: %s" % (self.ra_string(), s))
def warn(self, s):
common_warn("%s: %s" % (self.ra_string(), s))
def add_extra_stonith_params(self):
if not stonithd_metadata.mk_ra_node():
return
try:
params_node = self.doc.getElementsByTagName("parameters")[0]
except:
params_node = self.doc.createElement("parameters")
self.ra_node.appendChild(params_node)
for n in stonithd_metadata.ra_node.getElementsByTagName("parameter"):
params_node.appendChild(self.doc.importNode(n,1))
def mk_ra_node(self):
'''
Return the resource_agent node.
'''
if self.ra_node:
return self.ra_node
meta = self.meta()
try:
self.doc = xml.dom.minidom.parseString('\n'.join(meta))
except:
#common_err("could not parse meta-data for (%s,%s,%s)" \
# % (self.ra_class,self.ra_type,self.ra_provider))
self.ra_node = None
return None
try:
self.ra_node = self.doc.getElementsByTagName("resource-agent")[0]
except:
self.error("meta-data contains no resource-agent element")
self.ra_node = None
return None
if self.ra_class == "stonith":
self.add_extra_stonith_params()
return self.ra_node
def param_type_default(self,n):
try:
content = n.getElementsByTagName("content")[0]
type = content.getAttribute("type")
default = content.getAttribute("default")
return type,default
except:
return None,None
def params(self):
'''
Construct a dict of dicts: parameters are keys and
dictionary of attributes/values are values. Cached too.
'''
id = "ra_params-%s" % self.ra_string()
if wcache.is_cached(id):
return wcache.retrieve(id)
if not self.mk_ra_node():
return None
d = {}
for pset in self.ra_node.getElementsByTagName("parameters"):
for c in pset.getElementsByTagName("parameter"):
name = c.getAttribute("name")
if not name:
continue
required = c.getAttribute("required")
unique = c.getAttribute("unique")
type,default = self.param_type_default(c)
d[name] = {
"required": required,
"unique": unique,
"type": type,
"default": default,
}
return wcache.store(id,d)
def actions(self):
'''
Construct a dict of dicts: actions are keys and
dictionary of attributes/values are values. Cached too.
'''
id = "ra_actions-%s" % self.ra_string()
if wcache.is_cached(id):
return wcache.retrieve(id)
if not self.mk_ra_node():
return None
d = {}
for pset in self.ra_node.getElementsByTagName("actions"):
for c in pset.getElementsByTagName("action"):
name = c.getAttribute("name")
if not name or name in self.skip_ops:
continue
if name == "monitor":
name = monitor_name_node(c)
d[name] = {}
for a in c.attributes.keys():
if a in self.skip_op_attr:
continue
v = c.getAttribute(a)
if v:
d[name][a] = v
# add monitor ops without role, if they don't already
# exist
d2 = {}
for op in d.keys():
if re.match("monitor_[^0-9]", op):
norole_op = re.sub(r'monitor_[^0-9_]+_(.*)', r'monitor_\1', op)
if not norole_op in d:
d2[norole_op] = d[op]
d.update(d2)
return wcache.store(id,d)
def reqd_params_list(self):
'''
List of required parameters.
'''
d = self.params()
if not d: return []
return [x for x in d if d[x]["required"] == '1']
def param_default(self,pname):
'''
Parameter's default.
'''
d = self.params()
if not d: return None
return d[pname]["default"]
def sanity_check_params(self, id, pl):
'''
pl is a list of (attribute,value) pairs.
- are all required parameters defined
- do all parameters exist
'''
rc = 0
d = {}
for p,v in pl:
d[p] = v
for p in self.reqd_params_list():
if p not in d:
common_err("%s: required parameter %s not defined" % (id,p))
rc |= user_prefs.get_check_rc()
for p in d:
if p not in self.params():
common_err("%s: parameter %s does not exist" % (id,p))
rc |= user_prefs.get_check_rc()
return rc
def sanity_check_ops(self, id, ops):
'''
ops is a dict, operation names are keys and values are
lists of (attribute,value) pairs.
- do all operations exist
- are timeouts sensible
'''
rc = 0
n_ops = {}
for op in ops:
n_op = op == "monitor" and monitor_name_pl(ops[op]) or op
n_ops[n_op] = {}
for p,v in ops[op]:
if p in self.skip_op_attr:
continue
n_ops[n_op][p] = v
default_timeout = get_default("default-action-timeout")
for req_op in self.required_ops:
if req_op not in n_ops:
n_ops[req_op] = {}
for op in n_ops:
if op not in self.actions():
common_warn("%s: action %s not advertised in meta-data, it may not be supported by the RA" % (id,op))
rc |= 1
continue
try:
adv_timeout = self.actions()[op]["timeout"]
except:
continue
for a in n_ops[op]:
v = n_ops[op][a]
if a == "timeout":
if crm_msec(v) < 0:
continue
if crm_time_cmp(adv_timeout,v) > 0:
common_warn("%s: timeout %s for %s is smaller than the advised %s" % \
(id,v,op,adv_timeout))
rc |= 1
return rc
def meta(self):
'''
RA meta-data as raw xml.
'''
id = "ra_meta-%s" % self.ra_string()
if wcache.is_cached(id):
return wcache.retrieve(id)
if self.ra_class in ("pengine","stonithd"):
l = prog_meta(self.ra_class)
else:
l = ra_if.meta(self.ra_class,self.ra_type,self.ra_provider)
return wcache.store(id, l)
def meta_pretty(self):
'''
Print the RA meta-data in a human readable form.
'''
if not self.mk_ra_node():
return ''
l = []
title = self.meta_title()
l.append(title)
longdesc = get_nodes_text(self.ra_node,"longdesc")
if longdesc:
l.append(longdesc)
if self.ra_class != "heartbeat":
params = self.meta_parameters()
if params:
l.append(params.rstrip())
actions = self.meta_actions()
if actions:
l.append(actions)
return '\n\n'.join(l)
def get_shortdesc(self,n):
name = n.getAttribute("name")
shortdesc = get_nodes_text(n,"shortdesc")
longdesc = get_nodes_text(n,"longdesc")
if shortdesc and shortdesc not in (name,longdesc,self.ra_type):
return shortdesc
return ''
def meta_title(self):
s = self.ra_string()
shortdesc = self.get_shortdesc(self.ra_node)
if shortdesc:
s = "%s (%s)" % (shortdesc,s)
return s
def meta_param_head(self,n):
name = n.getAttribute("name")
if not name:
return None
s = name
if n.getAttribute("required") == "1":
s = s + "*"
type,default = self.param_type_default(n)
if type and default:
s = "%s (%s, [%s])" % (s,type,default)
elif type:
s = "%s (%s)" % (s,type)
shortdesc = self.get_shortdesc(n)
s = "%s: %s" % (s,shortdesc)
return s
def format_parameter(self,n):
l = []
head = self.meta_param_head(n)
if not head:
self.error("no name attribute for parameter")
return ""
l.append(head)
longdesc = get_nodes_text(n,"longdesc")
if longdesc:
longdesc = self.ra_tab + longdesc.replace("\n","\n"+self.ra_tab) + '\n'
l.append(longdesc)
return '\n'.join(l)
def meta_parameter(self,param):
if not self.mk_ra_node():
return ''
l = []
for pset in self.ra_node.getElementsByTagName("parameters"):
for c in pset.getElementsByTagName("parameter"):
if c.getAttribute("name") == param:
return self.format_parameter(c)
def meta_parameters(self):
if not self.mk_ra_node():
return ''
l = []
for pset in self.ra_node.getElementsByTagName("parameters"):
for c in pset.getElementsByTagName("parameter"):
s = self.format_parameter(c)
if s:
l.append(s)
if l:
return "Parameters (* denotes required, [] the default):\n\n" + '\n'.join(l)
def meta_action_head(self,n):
name = n.getAttribute("name")
if not name:
return ''
if name in self.skip_ops:
return ''
if name == "monitor":
name = monitor_name_node(n)
s = "%-13s" % name
for a in n.attributes.keys():
if a in self.skip_op_attr:
continue
v = n.getAttribute(a)
if v:
s = "%s %s=%s" % (s,a,v)
return s
def meta_actions(self):
l = []
for aset in self.ra_node.getElementsByTagName("actions"):
for c in aset.getElementsByTagName("action"):
s = self.meta_action_head(c)
if s:
l.append(self.ra_tab + s)
if l:
return "Operations' defaults (advisory minimum):\n\n" + '\n'.join(l)
def cmd_end(cmd,dir = ".."):
"Go up one level."
levels.droplevel()
def cmd_exit(cmd):
"Exit the crm program"
cmd_end(cmd)
if interactive:
print "bye"
try:
readline.write_history_file(hist_file)
except:
pass
for f in tmpfiles:
os.unlink(f)
sys.exit()
#
# help or make users feel less lonely
#
def add_shorthelp(topic,shorthelp,topic_help):
'''
Join topics ("%s,%s") if they share the same short
description.
'''
for i in range(len(topic_help)):
if topic_help[i][1] == shorthelp:
topic_help[i][0] = "%s,%s" % (topic_help[i][0], topic)
return
topic_help.append([topic, shorthelp])
def dump_short_help(help_tab):
topic_help = []
for topic in help_tab:
if topic == '.':
continue
# with odict, for whatever reason, python parses differently:
# help_tab["..."] = ("...","...") and
# help_tab["..."] = ("...","""
# ...""")
# a parser bug?
if type(help_tab[topic][0]) == type(()):
shorthelp = help_tab[topic][0][0]
else:
shorthelp = help_tab[topic][0]
add_shorthelp(topic,shorthelp,topic_help)
for t,d in topic_help:
print "\t%-16s %s" % (t,d)
def overview(help_tab):
print ""
print help_tab['.'][1]
print ""
print "Available commands:"
print ""
dump_short_help(help_tab)
print ""
def topic_help(help_tab,topic):
if topic not in help_tab:
print "There is no help for topic %s" % topic
return
if type(help_tab[topic][0]) == type(()):
shorthelp = help_tab[topic][0][0]
longhelp = help_tab[topic][0][1]
else:
shorthelp = help_tab[topic][0]
longhelp = help_tab[topic][1]
if longhelp:
page_string(longhelp)
else:
print shorthelp
def cmd_help(help_tab,topic = ''):
"help!"
# help_tab is an odict (ordered dictionary):
# help_tab[topic] = (short_help,long_help)
# topic '.' is a special entry for the top level
if not help_tab:
common_info("sorry, help not available")
return
if not topic:
overview(help_tab)
else:
topic_help(help_tab,topic)
class UserInterface(object):
'''
Stuff common to all user interface classes.
'''
def __init__(self):
self.cmd_table = odict()
self.cmd_table["help"] = (self.help,(0,1),0)
self.cmd_table["quit"] = (self.exit,(0,0),0)
self.cmd_table["end"] = (self.end,(0,1),0)
self.cmd_aliases = global_aliases.copy()
def end_game(self, no_questions_asked = False):
pass
def help(self,cmd,topic = ''):
"usage: help []"
cmd_help(self.help_table,topic)
def end(self,cmd,dir = ".."):
"usage: end"
self.end_game()
cmd_end(cmd,dir)
def exit(self,cmd):
"usage: exit"
self.end_game()
cmd_exit(cmd)
def add_sudo(cmd):
if user_prefs.crm_user:
return "sudo -E -u %s %s"%(user_prefs.crm_user,cmd)
return cmd
def pipe_string(cmd,s):
rc = -1 # command failed
cmd = add_sudo(cmd)
p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE)
try:
p.communicate(s)
p.wait()
rc = p.returncode
except IOError, msg:
common_err(msg)
return rc
def cibdump2doc(section = None):
doc = None
if section:
cmd = "%s -o %s" % (cib_dump,section)
else:
cmd = cib_dump
cmd = add_sudo(cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
try:
doc = xmlparse(p.stdout)
p.wait()
except IOError, msg:
common_err(msg)
return None
return doc
def file2doc(s):
try: f = open(s,'r')
except IOError, msg:
common_err(msg)
return None
doc = xmlparse(f)
f.close()
return doc
def shadow2doc(name):
return file2doc(shadowfile(name))
def str2tmp(s):
'''
Write the given string to a temporary file. Return the name
of the file.
'''
fd,tmp = mkstemp()
try: f = os.fdopen(fd,"w")
except IOError, msg:
common_err(msg)
return
f.write(s)
f.close()
return tmp
def is_filename_sane(name):
if re.search("['`/#*?$\[\]]",name):
common_err("%s: bad name"%name)
return False
return True
def is_name_sane(name):
if re.search("[']",name):
common_err("%s: bad name"%name)
return False
return True
def is_value_sane(name):
if re.search("[']",name):
common_err("%s: bad name"%name)
return False
return True
def ext_cmd(cmd):
if regression_tests:
print ".EXT", cmd
return subprocess.call(add_sudo(cmd), shell=True)
def get_stdout(cmd, stderr_on = True):
'''
Run a cmd, return stdin output.
stderr_on controls whether to show output which comes on stderr.
'''
if stderr_on:
stderr = None
else:
stderr = subprocess.PIPE
proc = subprocess.Popen(cmd, shell = True, \
stdout = subprocess.PIPE, stderr = stderr)
outp = proc.communicate()[0]
proc.wait()
outp = outp.strip()
return outp
def stdout2list(cmd, stderr_on = True):
'''
Run a cmd, fetch output, return it as a list of lines.
stderr_on controls whether to show output which comes on stderr.
'''
s = get_stdout(add_sudo(cmd), stderr_on)
return s.split('\n')
def find_program(envvar,*args):
if envvar and os.getenv(envvar):
return os.getenv(envvar)
for prog in args:
if is_program(prog):
return prog
def is_id_valid(id):
"""
Verify that the id follows the definition:
http://www.w3.org/TR/1999/REC-xml-names-19990114/#ns-qualnames
"""
if not id:
return False
id_re = "^[A-Za-z_][\w._-]*$"
return re.match(id_re,id)
def check_filename(fname):
"""
Verify that the string is a filename.
"""
fname_re = "^[^/]+$"
return re.match(fname_re,id)
class UserPrefs(object):
'''
Keep user preferences here.
'''
dflt_colorscheme = "yellow,normal,cyan,red,green,magenta".split(',')
skill_levels = {"operator":0, "administrator":1, "expert":2}
output_types = ("plain", "color", "uppercase")
check_frequencies = ("always", "on-verify", "never")
check_modes = ("strict", "relaxed")
def __init__(self):
self.skill_level = 2 #TODO: set back to 0?
self.editor = find_program("EDITOR","vim","vi","emacs","nano")
self.pager = find_program("PAGER","less","more","pg")
self.dotty = find_program("","dotty")
if not self.editor:
missing_prog_warn("editor")
if not self.pager:
missing_prog_warn("pager")
self.crm_user = ""
self.xmlindent = " " # two spaces
# keywords,ids,attribute names,values
self.colorscheme = self.dflt_colorscheme
# plain or color
self.output = ['color',]
# the semantic checks preferences
self.check_frequency = "always"
self.check_mode = "strict"
self.debug = False
self.force = False
def check_skill_level(self,n):
return self.skill_level >= n
def set_skill_level(self,skill_level):
if skill_level in self.skill_levels:
self.skill_level = self.skill_levels[skill_level]
else:
common_err("no %s skill level"%skill_level)
return False
def get_skill_level(self):
for s in self.skill_levels:
if self.skill_level == self.skill_levels[s]:
return s
def set_editor(self,prog):
if is_program(prog):
self.editor = prog
else:
common_err("program %s does not exist"% prog)
return False
def set_pager(self,prog):
if is_program(prog):
self.pager = prog
else:
common_err("program %s does not exist"% prog)
return False
def set_crm_user(self,user = ''):
self.crm_user = user
def set_output(self,otypes):
l = otypes.split(',')
for otype in l:
if not otype in self.output_types:
common_err("no %s output type" % otype)
return False
self.output = l
def set_colors(self,scheme):
colors = scheme.split(',')
if len(colors) != 6:
common_err("bad color scheme: %s"%scheme)
colors = UserPrefs.dflt_colorscheme
rc = True
for c in colors:
if not termctrl.is_color(c):
common_err("%s is not a recognized color" % c)
rc = False
if rc:
self.colorscheme = colors
else:
self.output.remove("color")
return rc
def is_check_always(self):
'''
Even though the frequency may be set to always, it doesn't
make sense to do that with non-interactive sessions.
'''
return interactive and self.check_frequency == "always"
def get_check_rc(self):
'''
If the check mode is set to strict, then on errors we
return 2 which is the code for error. Otherwise, we
pretend that errors are warnings.
'''
return self.check_mode == "strict" and 2 or 1
def set_check_freq(self,frequency):
if frequency not in self.check_frequencies:
common_err("no %s check frequency"%frequency)
return False
self.check_frequency = frequency
def set_check_mode(self,mode):
if mode not in self.check_modes:
common_err("no %s check mode"%mode)
return False
self.check_mode = mode
def set_debug(self):
self.debug = True
def get_debug(self):
return self.debug
def set_force(self):
self.force = True
def get_force(self):
return self.force
def write_rc(self,f):
print >>f, '%s "%s"' % ("editor",self.editor)
print >>f, '%s "%s"' % ("pager",self.pager)
print >>f, '%s "%s"' % ("user",self.crm_user)
print >>f, '%s "%s"' % ("skill-level",self.get_skill_level())
print >>f, '%s "%s"' % ("output", ','.join(self.output))
print >>f, '%s "%s"' % ("colorscheme", ','.join(self.colorscheme))
print >>f, '%s "%s"' % ("check-frequency",self.check_frequency)
print >>f, '%s "%s"' % ("check-mode",self.check_mode)
def save_options(self):
try: f = open(rc_file,"w")
except os.error,msg:
common_err("open: %s"%msg)
return
print >>f, 'options'
self.write_rc(f)
print >>f, 'end'
f.close()
class CliOptions(UserInterface):
'''
Manage user preferences
'''
def __init__(self):
UserInterface.__init__(self)
self.help_table = help_sys.load_level("options")
self.cmd_table["skill-level"] = (self.set_skill_level,(1,1),0,(skills_list,))
self.cmd_table["editor"] = (self.set_editor,(1,1),0)
self.cmd_table["pager"] = (self.set_pager,(1,1),0)
self.cmd_table["user"] = (self.set_crm_user,(0,1),0)
self.cmd_table["output"] = (self.set_output,(1,1),0)
self.cmd_table["colorscheme"] = (self.set_colors,(1,1),0)
self.cmd_table["check-frequency"] = (self.set_check_frequency,(1,1),0)
self.cmd_table["check-mode"] = (self.set_check_mode,(1,1),0)
self.cmd_table["save"] = (self.save_options,(0,0),0)
self.cmd_table["show"] = (self.show_options,(0,0),0)
setup_aliases(self)
def set_skill_level(self,cmd,skill_level):
"""usage: skill-level
level: operator | administrator | expert"""
return user_prefs.set_skill_level(skill_level)
def set_editor(self,cmd,prog):
"usage: editor "
return user_prefs.set_editor(prog)
def set_pager(self,cmd,prog):
"usage: pager "
return user_prefs.set_pager(prog)
def set_crm_user(self,cmd,user = ''):
"usage: user []"
return user_prefs.set_crm_user(user)
def set_output(self,cmd,otypes):
"usage: output "
return user_prefs.set_output(otypes)
def set_colors(self,cmd,scheme):
"usage: colorscheme "
return user_prefs.set_colors(scheme)
def set_check_frequency(self,cmd,freq):
"usage: check-frequence "
return user_prefs.set_check_freq(freq)
def set_check_mode(self,cmd,mode):
"usage: check-mode "
return user_prefs.set_check_mode(mode)
def show_options(self,cmd):
"usage: show"
return user_prefs.write_rc(sys.stdout)
def save_options(self,cmd):
"usage: save"
return user_prefs.save_options()
def end_game(self, no_questions_asked = False):
if no_questions_asked and not interactive:
self.save_options("save")
cib_dump = "cibadmin -Ql"
cib_piped = "cibadmin -p"
cib_upgrade = "cibadmin --upgrade --force"
cib_verify = "crm_verify -V -p"
class WCache(object):
"Cache stuff. A naive implementation."
def __init__(self):
self.lists = {}
self.stamp = time.time()
self.max_cache_age = 600 # seconds
def is_cached(self,name):
if time.time() - self.stamp > self.max_cache_age:
self.stamp = time.time()
self.clear()
return name in self.lists
def store(self,name,lst):
self.lists[name] = lst
return lst
def retrieve(self,name):
if self.is_cached(name):
return self.lists[name]
else:
return None
def clear(self):
self.lists = {}
def listshadows():
return stdout2list("ls @CRM_CONFIG_DIR@ | fgrep shadow. | sed 's/^shadow\.//'")
def shadowfile(name):
return "@CRM_CONFIG_DIR@/shadow.%s" % name
class CibShadow(UserInterface):
'''
CIB shadow management class
'''
envvar = "CIB_shadow"
extcmd = ">/dev/null &1" % self.extcmd)
except os.error:
no_prog_err(self.extcmd)
return False
return True
def new(self,cmd,name,*args):
"usage: new [withstatus] [force]"
if not is_filename_sane(name):
return False
new_cmd = "%s -c '%s'" % (self.extcmd,name)
for par in args:
if not par in ("force","--force","withstatus"):
syntax_err((cmd,name,par), context = 'new')
return False
if user_prefs.get_force() or "force" in args or "--force" in args:
new_cmd = "%s --force" % new_cmd
if ext_cmd(new_cmd) == 0:
common_info("%s shadow CIB created"%name)
self.use("use",name)
if "withstatus" in args:
cib_status.load("shadow:%s" % name)
def delete(self,cmd,name):
"usage: delete "
if not is_filename_sane(name):
return False
if cib_in_use == name:
common_err("%s shadow CIB is in use"%name)
return False
if ext_cmd("%s -D '%s' --force" % (self.extcmd,name)) == 0:
common_info("%s shadow CIB deleted"%name)
else:
common_err("failed to delete %s shadow CIB"%name)
return False
def reset(self,cmd,name):
"usage: reset "
if not is_filename_sane(name):
return False
if ext_cmd("%s -r '%s'" % (self.extcmd,name)) == 0:
common_info("copied live CIB to %s"%name)
else:
common_err("failed to copy live CIB to %s"%name)
return False
def commit(self,cmd,name):
"usage: commit "
if not is_filename_sane(name):
return False
if ext_cmd("%s -C '%s' --force" % (self.extcmd,name)) == 0:
common_info("commited '%s' shadow CIB to the cluster"%name)
wcache.clear()
else:
common_err("failed to commit the %s shadow CIB"%name)
return False
def diff(self,cmd):
"usage: diff"
s = get_stdout(add_sudo("%s -d" % self.extcmd_stdout))
page_string(s)
def list(self,cmd):
"usage: list"
if regression_tests:
for t in listshadows():
print t
else:
multicolumn(listshadows())
def _use(self,name,withstatus):
# Choose a shadow cib for further changes. If the name
# provided is empty, then choose the live (cluster) cib.
# Don't allow ' in shadow names
global cib_in_use
if not name or name == "live":
os.unsetenv(self.envvar)
cib_in_use = ""
if withstatus:
cib_status.load("live")
else:
os.putenv(self.envvar,name)
cib_in_use = name
if withstatus:
cib_status.load("shadow:%s" % name)
def use(self,cmd,name = '', withstatus = ''):
"usage: use [] [withstatus]"
# check the name argument
if name and not is_filename_sane(name):
return False
if name and name != "live":
if not os.access(shadowfile(name),os.F_OK):
common_err("%s: no such shadow CIB"%name)
return False
if withstatus and withstatus != "withstatus":
syntax_err((cmd,withstatus), context = 'use')
return False
# If invoked from configure
# take special precautions
try:
prev_level = levels.previous().myname()
except:
prev_level = ''
if prev_level != "cibconfig":
self._use(name,withstatus)
return True
if not cib_factory.has_cib_changed():
self._use(name,withstatus)
# new CIB: refresh the CIB factory
cib_factory.refresh()
return True
saved_cib = cib_in_use
self._use(name,'') # don't load the status yet
if not cib_factory.is_current_cib_equal(silent = True):
# user made changes and now wants to switch to a
# different and unequal CIB; we refuse to cooperate
common_err("the requested CIB is different from the current one")
if user_prefs.get_force():
common_info("CIB overwrite forced")
elif not ask("All changes will be dropped. Do you want to proceed?"):
self._use(saved_cib,'') # revert to the previous CIB
return False
self._use(name,withstatus) # now load the status too
return True
def get_var(l,key):
for s in l:
a = s.split()
if len(a) == 2 and a[0] == key:
return a[1]
return ''
def chk_var(l,key):
for s in l:
a = s.split()
if len(a) == 2 and a[0] == key and a[1]:
return True
return False
def chk_key(l,key):
for s in l:
a = s.split()
if len(a) >= 1 and a[0] == key:
return True
return False
def validate_template(l):
'Test for required stuff in a template.'
if not chk_var(l,'%name'):
common_err("invalid template: missing '%name'")
return False
if not chk_key(l,'%generate'):
common_err("invalid template: missing '%generate'")
return False
g = l.index('%generate')
if not (chk_key(l[0:g],'%required') or chk_key(l[0:g],'%optional')):
common_err("invalid template: missing '%required' or '%optional'")
return False
return True
def fix_tmpl_refs(l,id,pfx):
for i in range(len(l)):
l[i] = l[i].replace(id,pfx)
def fix_tmpl_refs_re(l,regex,repl):
for i in range(len(l)):
l[i] = re.sub(regex,repl,l[i])
class LoadTemplate(object):
'''
Load a template and its dependencies, generate a
configuration file which should be relatively easy and
straightforward to parse.
'''
edit_instructions = '''# Edit instructions:
#
# Add content only at the end of lines starting with '%%'.
# Only add content, don't remove or replace anything.
# The parameters following '%required' are not optional,
# unlike those following '%optional'.
# You may also add comments for future reference.'''
no_more_edit = '''# Don't edit anything below this line.'''
def __init__(self,name):
self.name = name
self.all_pre_gen = []
self.all_post_gen = []
self.all_pfx = []
def new_pfx(self,name):
i = 1
pfx = name
while pfx in self.all_pfx:
pfx = "%s_%d" % (name,i)
i += 1
self.all_pfx.append(pfx)
return pfx
def generate(self):
return '\n'.join([ \
"# Configuration: %s" % self.name, \
'', \
self.edit_instructions, \
'', \
'\n'.join(self.all_pre_gen), \
self.no_more_edit, \
'', \
'%generate', \
'\n'.join(self.all_post_gen)])
def write_config(self,name):
try:
f = open("%s/%s" % (Template.conf_dir, name),"w")
except os.error,msg:
common_err("open: %s"%msg)
return False
print >>f, self.generate()
f.close()
return True
def load_template(self,tmpl):
try:
f = open("%s/%s" % (Template.tmpl_dir, tmpl))
except os.error,msg:
common_err("open: %s"%msg)
return ''
l = (''.join(f)).split('\n')
if not validate_template(l):
return ''
common_info("pulling in template %s" % tmpl)
g = l.index('%generate')
pre_gen = l[0:g]
post_gen = l[g+1:]
name = get_var(pre_gen,'%name')
for s in l[0:g]:
if s.startswith('%depends_on'):
a = s.split()
if len(a) != 2:
common_warn("%s: wrong usage" % s)
continue
tmpl_id = a[1]
tmpl_pfx = self.load_template(a[1])
if tmpl_pfx:
fix_tmpl_refs(post_gen,'%'+tmpl_id,'%'+tmpl_pfx)
pfx = self.new_pfx(name)
fix_tmpl_refs(post_gen, '%_:', '%'+pfx+':')
# replace remaining %_, it may be useful at times
fix_tmpl_refs(post_gen, '%_', pfx)
v_idx = pre_gen.index('%required') or pre_gen.index('%optional')
pre_gen.insert(v_idx,'%pfx ' + pfx)
self.all_pre_gen += pre_gen
self.all_post_gen += post_gen
return pfx
def post_process(self, params):
pfx_re = '(%s)' % '|'.join(self.all_pfx)
for n in params:
fix_tmpl_refs(self.all_pre_gen, '%% '+n, "%% "+n+" "+params[n])
fix_tmpl_refs_re(self.all_post_gen, \
'%'+pfx_re+'([^:]|$)', r'\1\2')
# process %if ... [%else] ... %fi
rmidx_l = []
if_seq = False
for i in range(len(self.all_post_gen)):
s = self.all_post_gen[i]
if if_seq:
a = s.split()
if len(a) >= 1 and a[0] == '%fi':
if_seq = False
rmidx_l.append(i)
elif len(a) >= 1 and a[0] == '%else':
outcome = not outcome
rmidx_l.append(i)
else:
if not outcome:
rmidx_l.append(i)
continue
if not s:
continue
a = s.split()
if len(a) == 2 and a[0] == '%if':
outcome = not a[1].startswith('%') # not replaced -> false
if_seq = True
rmidx_l.append(i)
rmidx_l.reverse()
for i in rmidx_l:
del self.all_post_gen[i]
def listtemplates():
l = []
for f in os.listdir(Template.tmpl_dir):
if os.path.isfile("%s/%s" % (Template.tmpl_dir,f)):
l.append(f)
return l
def listconfigs():
l = []
for f in os.listdir(Template.conf_dir):
if os.path.isfile("%s/%s" % (Template.conf_dir,f)):
l.append(f)
return l
def check_transition(inp,state,possible_l):
if not state in possible_l:
common_err("input (%s) in wrong state %s" % (inp,state))
return False
return True
class Template(UserInterface):
'''
Configuration templates.
'''
conf_dir = "%s/%s" % (os.getenv("HOME"),".crmconf")
tmpl_dir = "@datadir@/@PACKAGE@/templates"
def __init__(self):
UserInterface.__init__(self)
self.help_table = help_sys.load_level("template")
self.cmd_table["new"] = (self.new,(2,),1,(null_list,templates_list,loop))
self.cmd_table["load"] = (self.load,(0,1),1,(config_list,))
self.cmd_table["edit"] = (self.edit,(0,1),1,(config_list,))
self.cmd_table["delete"] = (self.delete,(1,2),1,(config_list,))
self.cmd_table["show"] = (self.show,(0,1),0,(config_list,))
self.cmd_table["apply"] = (self.apply,(0,2),1,(config_list_method,config_list))
self.cmd_table["list"] = (self.list,(0,1),0)
setup_aliases(self)
self.init_dir()
self.curr_conf = ''
def init_dir(self):
'''Create the conf directory, link to templates'''
if not os.path.isdir(self.conf_dir):
try:
os.makedirs(self.conf_dir)
except os.error,msg:
common_err("makedirs: %s"%msg)
return
def get_depends(self,tmpl):
'''return a list of required templates'''
# Not used. May need it later.
try:
tf = open("%s/%s" % (self.tmpl_dir, tmpl),"r")
except os.error,msg:
common_err("open: %s"%msg)
return
l = []
for s in tf:
a = s.split()
if len(a) >= 2 and a[0] == '%depends_on':
l += a[1:]
tf.close()
return l
def replace_params(self,s,user_data):
change = False
for i in range(len(s)):
word = s[i]
for p in user_data:
# is parameter in the word?
pos = word.find('%' + p)
if pos < 0:
continue
endpos = pos + len('%' + p)
# and it isn't part of another word?
if re.match("[A-Za-z0-9]", word[endpos:endpos+1]):
continue
# if the value contains a space or
# it is a value of an attribute
# put quotes around it
if user_data[p].find(' ') >= 0 or word[pos-1:pos] == '=':
v = '"' + user_data[p] + '"'
else:
v = user_data[p]
word = word.replace('%' + p, v)
change = True # we did replace something
if change:
s[i] = word
if 'opt' in s:
if not change:
s = []
else:
s.remove('opt')
return s
def generate(self,l,user_data):
'''replace parameters (user_data) and generate output
'''
l2 = []
for piece in l:
piece2 = []
for s in piece:
s = self.replace_params(s,user_data)
if s:
piece2.append(' '.join(s))
if piece2:
l2.append(' \\\n\t'.join(piece2))
return '\n'.join(l2)
def process(self,config = ''):
'''Create a cli configuration from the current config'''
try:
f = open("%s/%s" % (self.conf_dir, config or self.curr_conf),'r')
except os.error,msg:
common_err("open: %s"%msg)
return ''
l = []
piece = []
user_data = {}
# states
START = 0; PFX = 1; DATA = 2; GENERATE = 3
state = START
global lineno
save_lineno = lineno
lineno = 0
rc = True
for inp in f:
lineno += 1
if inp.startswith('#'):
continue
if type(inp) == type(u''):
inp = inp.encode('ascii')
inp = inp.strip()
try:
s = shlex.split(inp)
except ValueError, msg:
common_err(msg)
continue
while '\n' in s:
s.remove('\n')
if not s:
if state == GENERATE and piece:
l.append(piece)
piece = []
elif s[0] in ("%name","%depends_on","%suggests"):
continue
elif s[0] == "%pfx":
if check_transition(inp,state,(START,DATA)) and len(s) == 2:
pfx = s[1]
state = PFX
elif s[0] == "%required":
if check_transition(inp,state,(PFX,)):
state = DATA
data_reqd = True
elif s[0] == "%optional":
if check_transition(inp,state,(PFX,DATA)):
state = DATA
data_reqd = False
elif s[0] == "%%":
if state != DATA:
common_warn("user data in wrong state %s" % state)
if len(s) < 2:
common_warn("parameter name missing")
elif len(s) == 2:
if data_reqd:
common_err("required parameter %s not set" % s[1])
rc = False
elif len(s) == 3:
user_data["%s:%s" % (pfx,s[1])] = s[2]
else:
common_err("%s: syntax error" % inp)
elif s[0] == "%generate":
if check_transition(inp,state,(DATA,)):
state = GENERATE
piece = []
elif state == GENERATE:
if s:
piece.append(s)
else:
common_err("<%s> unexpected" % inp)
if piece:
l.append(piece)
lineno = save_lineno
f.close()
if not rc:
return ''
return self.generate(l,user_data)
def new(self,cmd,name,*args):
"usage: new [ ...] [params name=value ...]"
if not is_filename_sane(name):
return False
if os.path.isfile("%s/%s" % (self.conf_dir, name)):
common_err("config %s exists; delete it first" % name)
return False
lt = LoadTemplate(name)
rc = True
mode = 0
params = {}
for s in args:
if mode == 0 and s == "params":
params["id"] = name
mode = 1
elif mode == 1:
a = s.split('=')
if len(a) != 2:
syntax_err(args, context = 'new')
rc = False
else:
params[a[0]] = a[1]
elif not lt.load_template(s):
rc = False
if rc:
lt.post_process(params)
if not rc or not lt.write_config(name):
return False
self.curr_conf = name
def config_exists(self,name):
if not is_filename_sane(name):
return False
if not os.path.isfile("%s/%s" % (self.conf_dir, name)):
common_err("%s: no such config" % name)
return False
return True
def delete(self,cmd,name,force = ''):
"usage: delete [force]"
if force:
if force != "force" and force != "--force":
syntax_err((cmd,force), context = 'delete')
return False
if not self.config_exists(name):
return False
if name == self.curr_conf:
if not force and not user_prefs.get_force() and \
not ask("Do you really want to remove config %s which is in use?" % self.curr_conf):
return False
else:
self.curr_conf = ''
os.remove("%s/%s" % (self.conf_dir, name))
def load(self,cmd,name = ''):
"usage: load []"
if not name:
self.curr_conf = ''
return True
if not self.config_exists(name):
return False
self.curr_conf = name
def edit(self,cmd,name = ''):
"usage: edit []"
if not name and not self.curr_conf:
common_err("please load a config first")
return False
if name:
if not self.config_exists(name):
return False
edit_file("%s/%s" % (self.conf_dir, name))
else:
edit_file("%s/%s" % (self.conf_dir, self.curr_conf))
def show(self,cmd,name = ''):
"usage: show []"
if not name and not self.curr_conf:
common_err("please load a config first")
return False
if name:
if not self.config_exists(name):
return False
print self.process(name)
else:
print self.process()
def apply(self,cmd,*args):
"usage: apply [] []"
method = "replace"
name = ''
if len(args) > 0:
i = 0
if args[0] in ("replace","update"):
method = args[0]
i += 1
if len(args) > i:
name = args[i]
if not name and not self.curr_conf:
common_err("please load a config first")
return False
if name:
if not self.config_exists(name):
return False
s = self.process(name)
else:
s = self.process()
if not s:
return False
tmp = str2tmp(s)
if not tmp:
return False
set_obj = mkset_obj("NOOBJ")
set_obj.import_file(method,tmp)
try: os.unlink(tmp)
except: pass
def list(self,cmd,templates = ''):
"usage: list [templates]"
if templates == "templates":
multicolumn(listtemplates())
else:
multicolumn(listconfigs())
def manage_attr(cmd,attr_ext_commands,*args):
if len(args) < 3:
bad_usage(cmd,' '.join(args))
return False
attr_cmd = None
try:
attr_cmd = attr_ext_commands[args[1]]
except KeyError:
bad_usage(cmd,' '.join(args))
return False
if not attr_cmd:
bad_usage(cmd,' '.join(args))
return False
if args[1] == 'set':
if len(args) == 4:
if not is_name_sane(args[0]) \
or not is_name_sane(args[2]) \
or not is_value_sane(args[3]):
return False
return ext_cmd(attr_cmd%(args[0],args[2],args[3])) == 0
else:
bad_usage(cmd,' '.join(args))
return False
elif args[1] in ('delete','show'):
if len(args) == 3:
if not is_name_sane(args[0]) \
or not is_name_sane(args[2]):
return False
return ext_cmd(attr_cmd%(args[0],args[2])) == 0
else:
bad_usage(cmd,' '.join(args))
return False
else:
bad_usage(cmd,' '.join(args))
return False
def resources_xml():
if wcache.is_cached("rsc_xml"):
return wcache.retrieve("rsc_xml")
doc = cibdump2doc("resources")
if not doc:
return []
return wcache.store("rsc_xml",doc)
def rsc2node(id):
if wcache.is_cached("rsc_%s_node" % id):
return wcache.retrieve("rsc_%s_node" % id)
doc = resources_xml()
if not doc:
return []
nodes = get_interesting_nodes(doc,[])
for n in nodes:
if is_resource(n) and n.getAttribute("id") == id:
return wcache.store("rsc_%s_node" % id, n)
def get_meta_param(id,param):
return get_stdout(RscMgmt.rsc_meta['show'] % (id,param), stderr_on = False)
def is_live_cib():
'''We working with the live cluster?'''
return not cib_in_use and not os.getenv("CIB_file")
def is_rsc_running(id):
if not is_live_cib():
return False
rsc_node = rsc2node(id)
if not rsc_node:
return False
if not is_resource(rsc_node):
return False
test_id = rsc_clone(id) or id
outp = get_stdout(RscMgmt.rsc_status % test_id, stderr_on = False)
return outp.find("running") > 0 and outp.find("NOT") == -1
def is_rsc_clone(rsc_id):
rsc_node = rsc2node(rsc_id)
return is_clone(rsc_node)
def is_rsc_ms(rsc_id):
rsc_node = rsc2node(rsc_id)
return is_ms(rsc_node)
def rsc_clone(rsc_id):
'''Get a clone of a resource.'''
rsc_node = rsc2node(rsc_id)
if not rsc_node or not rsc_node.parentNode:
return None
pnode = rsc_node.parentNode
if is_group(pnode):
pnode = pnode.parentNode
if is_clonems(pnode):
return pnode.getAttribute("id")
def is_process(s):
proc = subprocess.Popen("ps -e -o pid,command | grep -qs '%s'" % s, \
shell=True, stdout=subprocess.PIPE)
proc.wait()
return proc.returncode == 0
def cluster_stack():
if is_process("heartbeat:.[m]aster"):
return "heartbeat"
elif is_process("[a]isexec"):
return "openais"
return ""
def get_cloned_rsc(rsc_id):
rsc_node = rsc2node(rsc_id)
if not rsc_node:
return ""
for c in rsc_node.childNodes:
if is_child_rsc(c):
return c.getAttribute("id")
return ""
def get_max_clone(id):
v = get_meta_param(id,"clone-max")
try:
cnt = int(v)
except:
cnt = len(listnodes())
return cnt
def cleanup_resource(rsc,node):
if not is_name_sane(rsc) or not is_name_sane(node):
return False
if is_rsc_clone(rsc) or is_rsc_ms(rsc):
base = get_cloned_rsc(rsc)
if not base:
return False
clone_max = get_max_clone(rsc)
rc = True
for n in range(clone_max):
if ext_cmd(RscMgmt.rsc_cleanup % ("%s:%d" % (base,n), node)) != 0:
rc = False
else:
rc = ext_cmd(RscMgmt.rsc_cleanup%(rsc,node)) != 0
return rc
class RscMgmt(UserInterface):
'''
Resources management class
'''
rsc_status_all = "crm_resource -L"
rsc_status = "crm_resource -W -r '%s'"
rsc_showxml = "crm_resource -q -r '%s'"
rsc_setrole = "crm_resource --meta -r '%s' -p target-role -v '%s'"
rsc_manage = "crm_resource --meta -r '%s' -p is-managed -v '%s'"
rsc_migrate = "crm_resource -M -r '%s'"
rsc_migrateto = "crm_resource -M -r '%s' -H '%s'"
rsc_unmigrate = "crm_resource -U -r '%s'"
rsc_cleanup = "crm_resource -C -r '%s' -H '%s'"
rsc_param = {
'set': "crm_resource -r '%s' -p '%s' -v '%s'",
'delete': "crm_resource -r '%s' -d '%s'",
'show': "crm_resource -r '%s' -g '%s'",
}
rsc_meta = {
'set': "crm_resource --meta -r '%s' -p '%s' -v '%s'",
'delete': "crm_resource --meta -r '%s' -d '%s'",
'show': "crm_resource --meta -r '%s' -g '%s'",
}
rsc_failcount = {
'set': "crm_failcount -r '%s' -N '%s' -v '%s'",
'delete': "crm_failcount -r '%s' -N '%s' -D",
'show': "crm_failcount -r '%s' -N '%s' -G",
}
rsc_refresh = "crm_resource -R"
rsc_refresh_node = "crm_resource -R -H '%s'"
rsc_reprobe = "crm_resource -P"
rsc_reprobe_node = "crm_resource -P -H '%s'"
def __init__(self):
UserInterface.__init__(self)
self.help_table = help_sys.load_level("resource")
self.cmd_table["status"] = (self.status,(0,1),0,(rsc_list,))
self.cmd_table["start"] = (self.start,(1,1),0,(rsc_list,))
self.cmd_table["stop"] = (self.stop,(1,1),0,(rsc_list,))
self.cmd_table["restart"] = (self.restart,(1,1),0,(rsc_list,))
self.cmd_table["promote"] = (self.promote,(1,1),0,(rsc_list,))
self.cmd_table["demote"] = (self.demote,(1,1),0,(rsc_list,))
self.cmd_table["manage"] = (self.manage,(1,1),0,(rsc_list,))
self.cmd_table["unmanage"] = (self.unmanage,(1,1),0,(rsc_list,))
self.cmd_table["migrate"] = (self.migrate,(1,2),0,(rsc_list,nodes_list))
self.cmd_table["unmigrate"] = (self.unmigrate,(1,1),0,(rsc_list,))
self.cmd_table["param"] = (self.param,(3,4),1,(rsc_list,attr_cmds))
self.cmd_table["meta"] = (self.meta,(3,4),1,(rsc_list,attr_cmds))
self.cmd_table["failcount"] = (self.failcount,(3,4),0,(rsc_list,attr_cmds,nodes_list))
self.cmd_table["cleanup"] = (self.cleanup,(1,2),1,(rsc_list,nodes_list))
self.cmd_table["refresh"] = (self.refresh,(0,1),0,(nodes_list,))
self.cmd_table["reprobe"] = (self.reprobe,(0,1),0,(nodes_list,))
self.cmd_aliases.update({
"status": ("show","list",),
"migrate": ("move",),
"unmigrate": ("unmove",),
})
setup_aliases(self)
def status(self,cmd,rsc = None):
"usage: status []"
if rsc:
if not is_name_sane(rsc):
return False
return ext_cmd(self.rsc_status % rsc) == 0
else:
return ext_cmd(self.rsc_status_all) == 0
def start(self,cmd,rsc):
"usage: start "
if not is_name_sane(rsc):
return False
return ext_cmd(self.rsc_setrole%(rsc,"Started")) == 0
def restart(self,cmd,rsc):
"usage: restart "
if not is_name_sane(rsc):
return False
if not self.stop("stop",rsc):
return False
return self.start("start",rsc)
def stop(self,cmd,rsc):
"usage: stop "
if not is_name_sane(rsc):
return False
return ext_cmd(self.rsc_setrole%(rsc,"Stopped")) == 0
def promote(self,cmd,rsc):
"usage: promote "
if not is_name_sane(rsc):
return False
if not is_rsc_ms(rsc):
common_err("%s is not a master-slave resource" % rsc)
return False
return ext_cmd(self.rsc_setrole%(rsc,"Master")) == 0
def demote(self,cmd,rsc):
"usage: demote "
if not is_name_sane(rsc):
return False
if not is_rsc_ms(rsc):
common_err("%s is not a master-slave resource" % rsc)
return False
return ext_cmd(self.rsc_setrole%(rsc,"Slave")) == 0
def manage(self,cmd,rsc):
"usage: manage "
if not is_name_sane(rsc):
return False
return ext_cmd(self.rsc_manage%(rsc,"true")) == 0
def unmanage(self,cmd,rsc):
"usage: unmanage "
if not is_name_sane(rsc):
return False
return ext_cmd(self.rsc_manage%(rsc,"false")) == 0
def migrate(self,cmd,*args):
"""usage: migrate []"""
if not is_name_sane(args[0]):
return False
if len(args) == 1:
return ext_cmd(self.rsc_migrate%args[0]) == 0
else:
if not is_name_sane(args[1]):
return False
return ext_cmd(self.rsc_migrateto%(args[0],args[1])) == 0
def unmigrate(self,cmd,rsc):
"usage: unmigrate "
if not is_name_sane(rsc):
return False
return ext_cmd(self.rsc_unmigrate%rsc) == 0
def cleanup(self,cmd,*args):
"usage: cleanup []"
# Cleanup a resource on a node. Omit node to cleanup on
# all live nodes.
if len(args) == 2: # remove
return cleanup_resource(args[0],args[1])
else:
rv = True
for n in listnodes():
if not cleanup_resource(args[0],n):
rv = False
return rv
def failcount(self,cmd,*args):
"""usage:
failcount set
failcount delete
failcount show """
d = lambda: manage_attr(cmd,self.rsc_failcount,*args)
return d()
def param(self,cmd,*args):
"""usage:
param set
param delete
param show """
d = lambda: manage_attr(cmd,self.rsc_param,*args)
return d()
def meta(self,cmd,*args):
"""usage:
meta set
meta delete
meta show """
d = lambda: manage_attr(cmd,self.rsc_meta,*args)
return d()
def refresh(self,cmd,*args):
'usage: refresh []'
if len(args) == 1:
if not is_name_sane(args[0]):
return False
return ext_cmd(self.rsc_refresh_node%args[0]) == 0
else:
return ext_cmd(self.rsc_refresh) == 0
def reprobe(self,cmd,*args):
'usage: reprobe []'
if len(args) == 1:
if not is_name_sane(args[0]):
return False
return ext_cmd(self.rsc_reprobe_node%args[0]) == 0
else:
return ext_cmd(self.rsc_reprobe) == 0
def print_node(uname,id,node_type,other,inst_attr,offline):
"""
Try to pretty print a node from the cib. Sth like:
uname(id): node_type
attr1: v1
attr2: v2
"""
s_offline = offline and "(offline)" or ""
if uname == id:
print "%s: %s%s" % (uname,node_type,s_offline)
else:
print "%s(%s): %s%s" % (uname,id,node_type,s_offline)
for a in other:
print "\t%s: %s" % (a,other[a])
for a,v in inst_attr:
print "\t%s: %s" % (a,v)
class NodeMgmt(UserInterface):
'''
Nodes management class
'''
node_standby = "crm_standby -N '%s' -v '%s'"
node_delete = "cibadmin -D -o nodes -X ''"
node_delete_status = "cibadmin -D -o status -X ''"
hb_delnode = "@libdir@/heartbeat/hb_delnode '%s'"
crm_node = "crm_node"
node_fence = "crm_attribute -t status -U '%s' -n terminate -v true"
dc = "crmadmin -D"
node_attr = {
'set': "crm_attribute -t nodes -U '%s' -n '%s' -v '%s'",
'delete': "crm_attribute -D -t nodes -U '%s' -n '%s'",
'show': "crm_attribute -G -t nodes -U '%s' -n '%s'",
}
node_status = {
'set': "crm_attribute -t status -U '%s' -n '%s' -v '%s'",
'delete': "crm_attribute -D -t status -U '%s' -n '%s'",
'show': "crm_attribute -G -t status -U '%s' -n '%s'",
}
def __init__(self):
UserInterface.__init__(self)
self.help_table = help_sys.load_level("node")
self.cmd_table["status"] = (self.status,(0,1),0,(nodes_list,))
self.cmd_table["show"] = (self.show,(0,1),0,(nodes_list,))
self.cmd_table["standby"] = (self.standby,(0,1),0,(nodes_list,))
self.cmd_table["online"] = (self.online,(0,1),0,(nodes_list,))
self.cmd_table["fence"] = (self.fence,(1,1),0,(nodes_list,))
self.cmd_table["delete"] = (self.delete,(1,1),0,(nodes_list,))
self.cmd_table["attribute"] = (self.attribute,(3,4),0,(nodes_list,attr_cmds))
self.cmd_table["status-attr"] = (self.status_attr,(3,4),0,(nodes_list,attr_cmds))
self.cmd_aliases.update({
"show": ("list",),
})
setup_aliases(self)
def status(self,cmd,node = None):
'usage: status []'
return ext_cmd("%s -o nodes"%cib_dump) == 0
def show(self,cmd,node = None):
'usage: show []'
doc = cibdump2doc()
if not doc:
return False
nodes_node = get_conf_elem(doc, "nodes")
status = get_conf_elem(doc, "status")
if not nodes_node:
return False
for c in nodes_node.childNodes:
if not is_element(c) or c.tagName != "node":
continue
if node and c.getAttribute("uname") != node:
continue
type = uname = id = ""
inst_attr = []
other = {}
for attr in c.attributes.keys():
v = c.getAttribute(attr)
if attr == "type":
type = v
elif attr == "uname":
uname = v
elif attr == "id":
id = v
else:
other[attr] = v
for c2 in c.childNodes:
if not is_element(c2):
continue
if c2.tagName == "instance_attributes":
inst_attr += nvpairs2list(c2)
offline = False
for c2 in status.getElementsByTagName("node_state"):
if uname != c2.getAttribute("uname"):
continue
offline = c2.getAttribute("crmd") == "offline"
print_node(uname,id,type,other,inst_attr,offline)
def standby(self,cmd,node = None):
'usage: standby []'
if not node:
node = this_node
if not is_name_sane(node):
return False
return ext_cmd(self.node_standby%(node,"on")) == 0
def online(self,cmd,node = None):
'usage: online []'
if not node:
node = this_node
if not is_name_sane(node):
return False
return ext_cmd(self.node_standby%(node,"off")) == 0
def fence(self,cmd,node):
'usage: fence '
if not node:
node = this_node
if not is_name_sane(node):
return False
return ext_cmd(self.node_fence%(node)) == 0
def delete(self,cmd,node):
'usage: delete '
if not is_name_sane(node):
return False
rc = True
if cluster_stack() == "heartbeat":
rc = ext_cmd(self.hb_delnode%node) == 0
else:
node_states = {}
for s in stdout2list("%s -l" % self.crm_node):
a = s.split()
if len(a) != 3:
common_warn("%s bad format: %s" % (self.crm_node,s))
continue
# fmt: id uname status
# remove only those in state "lost"
if a[1] == node:
node_states[a[2]] = 1
if a[2] == "lost":
if ext_cmd("%s --force -R %s" % (self.crm_node,a[0])) != 0:
rc = False
if not "lost" in node_states:
common_err('node %s/state "lost" not found in the id list' % node)
if "member" in node_states:
common_info("node %s appears to be still active" % node)
common_info("check output of %s -l" % self.crm_node)
rc = False
if rc:
if ext_cmd(self.node_delete%node) != 0 or \
ext_cmd(self.node_delete_status%node) != 0:
rc = False
return rc
def attribute(self,cmd,*args):
"""usage:
attribute set
attribute delete
attribute show """
d = lambda: manage_attr(cmd,self.node_attr,*args)
return d()
def status_attr(self,cmd,*args):
"""usage:
status-attr set
status-attr delete
status-attr show """
d = lambda: manage_attr(cmd,self.node_status,*args)
return d()
def edit_file(fname):
'Edit a file.'
if not fname:
return
if not user_prefs.editor:
return
return ext_cmd("%s %s" % (user_prefs.editor,fname))
def page_string(s):
'Write string through a pager.'
if not s:
return
w,h = get_winsize()
if s.count('\n') <= h:
print s
elif not user_prefs.pager or not interactive:
print s
else:
opts = ""
if user_prefs.pager == "less":
opts = "-R"
pipe_string("%s %s" % (user_prefs.pager,opts), s)
def lines2cli(s):
'''
Convert a string into a list of lines. Replace continuation
characters. Strip white space, left and right. Drop empty lines.
'''
cl = []
l = s.split('\n')
cum = []
for p in l:
p = p.strip()
if p.endswith('\\'):
p = p.rstrip('\\')
cum.append(p)
else:
cum.append(p)
cl.append(''.join(cum).strip())
cum = []
if cum: # in case s ends with backslash
cl.append(''.join(cum))
return [x for x in cl if x]
def get_winsize():
try:
import curses
curses.setupterm()
w = curses.tigetnum('cols')
h = curses.tigetnum('lines')
except:
try:
w = os.environ['COLS']
h = os.environ['LINES']
except:
w = 80; h = 25
return w,h
def multicolumn(l):
'''
A ls-like representation of a list of strings.
A naive approach.
'''
min_gap = 2
w,h = get_winsize()
max_len = 8
for s in l:
if len(s) > max_len:
max_len = len(s)
cols = w/(max_len + min_gap) # approx.
col_len = w/cols
for i in range(len(l)/cols + 1):
s = ''
for j in range(i*cols,(i+1)*cols):
if not j < len(l):
break
if not s:
s = "%-*s" % (col_len,l[j])
elif (j+1)%cols == 0:
s = "%s%s" % (s,l[j])
else:
s = "%s%-*s" % (s,col_len,l[j])
if s:
print s
class RA(UserInterface):
'''
CIB shadow management class
'''
provider_classes = ["ocf"]
def __init__(self):
UserInterface.__init__(self)
self.help_table = help_sys.load_level("ra")
self.cmd_table["classes"] = (self.classes,(0,0),0)
self.cmd_table["list"] = (self.list,(1,2),1)
self.cmd_table["providers"] = (self.providers,(1,1),1)
self.cmd_table["meta"] = (self.meta,(1,3),1)
self.cmd_aliases.update({
"meta": ("info",),
})
setup_aliases(self)
def classes(self,cmd):
"usage: classes"
for c in ra_classes():
if c in self.provider_classes:
print "%s / %s" % (c,' '.join(ra_providers_all(c)))
else:
print "%s" % c
def providers(self,cmd,ra_type):
"usage: providers "
print ' '.join(ra_providers(ra_type))
def list(self,cmd,c,p = None):
"usage: list []"
if not c in ra_classes():
common_err("class %s does not exist" % c)
return False
if p and not p in ra_providers_all(c):
common_err("there is no provider %s for class %s" % (p,c))
return False
if regression_tests:
for t in ra_types(c,p):
print t
else:
multicolumn(ra_types(c,p))
def meta(self,cmd,*args):
"usage: meta [:[:]]"
if len(args) > 1: # obsolete syntax
ra_type = args[0]
ra_class = args[1]
if len(args) < 3:
ra_provider = "heartbeat"
else:
ra_provider = args[2]
else:
ra_class,ra_provider,ra_type = disambiguate_ra_type(args[0])
ra = RAInfo(ra_class,ra_type,ra_provider)
if not ra.mk_ra_node():
return False
try:
page_string(ra.meta_pretty())
except:
return False
class StatusMgmt(UserInterface):
'''
The CIB status section management user interface class
'''
lrm_exit_codes = {
"success": "0",
"unknown": "1",
"args": "2",
"unimplemented": "3",
"perm": "4",
"installed": "5",
"configured": "6",
"not_running": "7",
"master": "8",
"failed_master": "9",
}
lrm_status_codes = {
"pending": "-1",
"done": "0",
"cancelled": "1",
"timeout": "2",
"notsupported": "3",
"error": "4",
}
ra_operations = ("probe", "monitor", "start", "stop",
"promote", "demote", "notify", "migrate_to", "migrate_from")
node_states = ("online", "offline", "unclean")
def __init__(self):
UserInterface.__init__(self)
self.help_table = help_sys.load_level("cibstatus")
self.cmd_table["show"] = (self.show,(0,1),1)
self.cmd_table["save"] = (self.save,(0,1),2)
self.cmd_table["load"] = (self.load,(1,1),2)
self.cmd_table["origin"] = (self.origin,(0,0),1)
self.cmd_table["node"] = (self.edit_node,(2,2),2,(status_node_list,node_states_list))
self.cmd_table["op"] = (self.edit_op,(3,5),2,(ra_operations_list,status_rsc_list,lrm_exit_codes_list,lrm_status_codes_list,status_node_list))
setup_aliases(self)
def myname(self):
'''Just return some id.'''
return "cibstatus"
def load(self,cmd,org):
"usage: load {|shadow:|live}"
return cib_status.load(org)
def save(self,cmd,dest = None):
"usage: save [|shadow:]"
return cib_status.save(dest)
def origin(self,cmd):
"usage: origin"
state = cib_status.modified and " (modified)" or ""
print "%s%s" % (cib_status.origin,state)
def show(self,cmd,changed = ""):
"usage: show [changed]"
if changed:
if changed != "changed":
syntax_err((cmd,changed))
return False
else:
return cib_status.list_changes()
return cib_status.show()
def edit_node(self,cmd,node,state):
"usage: node {online|offline|unclean}"
return cib_status.edit_node(node,state)
def edit_op(self,cmd,op,rsc,rc,op_status = None,node = ''):
"usage: op [] []"
if rc in self.lrm_exit_codes:
num_rc = self.lrm_exit_codes[rc]
else:
num_rc = rc
if not num_rc.isdigit():
common_err("%s exit code invalid" % num_rc)
return False
num_op_status = op_status
if op_status:
if op_status in self.lrm_status_codes:
num_op_status = self.lrm_status_codes[op_status]
if not num_op_status.isdigit():
common_err("%s operation status invalid" % num_op_status)
return False
return cib_status.edit_op(op,rsc,num_rc,num_op_status,node)
class CibConfig(UserInterface):
'''
The configuration class
'''
def __init__(self):
UserInterface.__init__(self)
self.help_table = help_sys.load_level("configure")
self.cmd_table["erase"] = (self.erase,(0,1),1)
self.cmd_table["verify"] = (self.verify,(0,0),1)
self.cmd_table["refresh"] = (self.refresh,(0,0),1)
self.cmd_table["ptest"] = (self.ptest,(0,3),1)
self.cmd_table["commit"] = (self.commit,(0,1),1)
self.cmd_table["upgrade"] = (self.upgrade,(0,1),1)
self.cmd_table["show"] = (self.show,(0,),1,(id_xml_list,id_list,loop))
self.cmd_table["edit"] = (self.edit,(0,),1,(id_xml_list,id_list,loop))
self.cmd_table["delete"] = (self.delete,(1,),1,(id_list,loop))
self.cmd_table["rename"] = (self.rename,(2,2),1,(id_list,))
self.cmd_table["save"] = (self.save,(1,2),1)
self.cmd_table["load"] = (self.load,(2,3),1)
self.cmd_table["node"] = (self.conf_node,(1,),1)
self.cmd_table["primitive"] = (self.conf_primitive,(2,),1,(null_list, \
ra_classes_list, primitive_complete_complex, loop))
self.cmd_table["group"] = (self.conf_group,(2,),1,(null_list,f_prim_id_list,loop))
self.cmd_table["clone"] = (self.conf_clone,(2,),1,(null_list,f_children_id_list))
self.cmd_table["ms"] = (self.conf_ms,(2,),1,(null_list,f_children_id_list))
self.cmd_table["location"] = (self.conf_location,(2,),1,(null_list,rsc_id_list))
self.cmd_table["colocation"] = (self.conf_colocation,(2,),1,(null_list,null_list,rsc_id_list,loop))
self.cmd_table["order"] = (self.conf_order,(2,),1,(null_list,null_list,rsc_id_list,loop))
self.cmd_table["property"] = (self.conf_property,(1,),1,(property_complete,loop))
self.cmd_table["rsc_defaults"] = (self.conf_rsc_defaults,(1,),1)
self.cmd_table["op_defaults"] = (self.conf_op_defaults,(1,),1)
self.cmd_table["monitor"] = (self.conf_monitor,(2,2),1)
self.cmd_table["ra"] = RA
self.cmd_table["cib"] = CibShadow
self.cmd_table["cibstatus"] = StatusMgmt
self.cmd_table["template"] = Template
self.cmd_table["_test"] = (self.check_structure,(0,0),1)
self.cmd_table["_regtest"] = (self.regression_testing,(1,1),1)
self.cmd_table["_queues"] = (self.showqueues,(0,0),1)
self.cmd_table["_objects"] = (self.showobjects,(0,0),1)
self.cmd_aliases.update({
"colocation": ("collocation",),
"ms": ("master",),
})
setup_aliases(self)
cib_factory.initialize()
def myname(self):
'''Just return some id.'''
return "cibconfig"
def check_structure(self,cmd):
return cib_factory.check_structure()
def regression_testing(self,cmd,param):
return cib_factory.regression_testing(param)
def showqueues(self,cmd):
cib_factory.showqueues()
def showobjects(self,cmd):
cib_factory.showobjects()
def show(self,cmd,*args):
"usage: show [xml] [...]"
if not cib_factory.is_cib_sane():
return False
err_buf.buffer() # keep error messages
set_obj = mkset_obj(*args)
err_buf.release() # show them, but get an ack from the user
return set_obj.show()
def edit(self,cmd,*args):
"usage: edit [xml] [...]"
if not cib_factory.is_cib_sane():
return False
err_buf.buffer() # keep error messages
set_obj = mkset_obj(*args)
err_buf.release() # show them, but get an ack from the user
return set_obj.edit()
def verify(self,cmd):
"usage: verify"
if not cib_factory.is_cib_sane():
return False
set_obj = mkset_obj("xml")
rc1 = set_obj.verify()
if user_prefs.check_frequency != "never":
rc2 = set_obj.verify2()
else:
rc2 = 0
return rc1 and rc2 <= 1
def save(self,cmd,*args):
"usage: save [xml] "
if not cib_factory.is_cib_sane():
return False
if args[0] == "xml":
f = args[1]
set_obj = mkset_obj("xml")
else:
f = args[0]
set_obj = mkset_obj()
return set_obj.save_to_file(f)
def load(self,cmd,*args):
"usage: load [xml] {replace|update} {|}"
if not cib_factory.is_cib_sane():
return False
if args[0] == "xml":
if len(args) != 3:
syntax_err(args, context = 'load')
return False
url = args[2]
method = args[1]
set_obj = mkset_obj("xml","NOOBJ")
else:
if len(args) != 2:
syntax_err(args, context = 'load')
return False
url = args[1]
method = args[0]
set_obj = mkset_obj("NOOBJ")
return set_obj.import_file(method,url)
def delete(self,cmd,*args):
"usage: delete [...]"
if not cib_factory.is_cib_sane():
return False
return cib_factory.delete(*args)
def rename(self,cmd,old_id,new_id):
"usage: rename "
if not cib_factory.is_cib_sane():
return False
return cib_factory.rename(old_id,new_id)
def erase(self,cmd,nodes = None):
"usage: erase [nodes]"
if not cib_factory.is_cib_sane():
return False
if nodes:
if nodes == "nodes":
return cib_factory.erase_nodes()
else:
syntax_err((cmd,nodes), context = 'erase')
else:
return cib_factory.erase()
def refresh(self,cmd):
"usage: refresh"
if not cib_factory.is_cib_sane():
return False
if interactive and cib_factory.has_cib_changed():
if not ask("All changes will be dropped. Do you want to proceed?"):
return
cib_factory.refresh()
def ptest(self,cmd,*args):
"usage: ptest [nograph] [v...] [scores]"
if not cib_factory.is_cib_sane():
return False
verbosity = 'vv' # default verbosity
nograph = False
scores = False
for p in args:
if p == "nograph":
nograph = True
elif p == "scores":
scores = True
elif re.match("^vv*$", p):
verbosity = p
else:
bad_usage(cmd,' '.join(args))
return False
set_obj = mkset_obj("xml")
return set_obj.ptest(nograph, scores, verbosity)
def commit(self,cmd,force = None):
"usage: commit [force]"
if force and force != "force":
syntax_err((cmd,force))
return False
if not cib_factory.is_cib_sane():
return False
if not cib_factory.has_cib_changed():
common_info("apparently there is nothing to commit")
common_info("try changing something first")
return
wcache.clear()
rc1 = cib_factory.is_current_cib_equal()
rc2 = self.verify("verify")
if rc1 and rc2:
return cib_factory.commit()
if force or user_prefs.get_force():
common_info("commit forced")
return cib_factory.commit()
if ask("Do you still want to commit?"):
return cib_factory.commit()
return False
def upgrade(self,cmd,force = None):
"usage: upgrade [force]"
if not cib_factory.is_cib_sane():
return False
if force and force != "force":
syntax_err((cmd,force))
return False
if user_prefs.get_force() or force:
return cib_factory.upgrade_cib_06to10(True)
else:
return cib_factory.upgrade_cib_06to10()
def __conf_object(self,cmd,*args):
"The configure object command."
if not cib_factory.is_cib_sane():
return False
f = lambda: cib_factory.create_object(cmd,*args)
return f()
def conf_node(self,cmd,*args):
"""usage: node [:]
[attributes = [=...]]"""
return self.__conf_object(cmd,*args)
def conf_primitive(self,cmd,*args):
"""usage: primitive [:[:]]
[params = [=...]]
[meta = [=...]]
[operations id_spec
[op op_type [=...] ...]]"""
return self.__conf_object(cmd,*args)
def conf_group(self,cmd,*args):
"""usage: group [...]
[params = [=...]]
[meta = [=...]]"""
return self.__conf_object(cmd,*args)
def conf_clone(self,cmd,*args):
"""usage: clone
[params = [=...]]
[meta = [=...]]"""
return self.__conf_object(cmd,*args)
def conf_ms(self,cmd,*args):
"""usage: ms
[params = [=...]]
[meta = [=...]]"""
return self.__conf_object(cmd,*args)
def conf_location(self,cmd,*args):
"""usage: location {node_pref|rules}
node_pref :: :
rules ::
rule [id_spec] [$role=] :
[rule [id_spec] [$role=] : ...]
id_spec :: $id= | $id-ref=
score :: | | [-]inf
expression :: [bool_op ...]
bool_op :: or | and
simple_exp :: [type:]
|
| date
type :: string | version | number
binary_op :: lt | gt | lte | gte | eq | ne
unary_op :: defined | not_defined"""
return self.__conf_object(cmd,*args)
def conf_colocation(self,cmd,*args):
"""usage: colocation : [:] [:]
"""
return self.__conf_object(cmd,*args)
def conf_order(self,cmd,*args):
"""usage: order score-type: [:] [:]
[symmetrical=]"""
return self.__conf_object(cmd,*args)
def conf_property(self,cmd,*args):
"usage: property [$id=]